/*
 * ScriptCache.java
 *
 * Created on July 18, 2007, 2:07 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.www.util.script;

import org.atomojo.www.util.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HttpsURLConnection;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;

/**
 *
 * @author alex
 */
public class ScriptCache
{
   static Logger LOG = Logger.getLogger(ScriptCache.class.getName());
   
   class Location {
      URI location;
      String username;
      String password;
      String contentType;
      Location(URI location,String username, String password)
      {
         this.location = location;
         this.username = username;
         this.password = password==null ? "" : password;
         this.contentType = null;
      }
      
      String getContentType() {
         return contentType;
      }
      
      Reader open() 
         throws IOException
      {
         URLConnection connection = location.toURL().openConnection();
         if (connection instanceof HttpsURLConnection) {
            HttpsURLConnection https = (HttpsURLConnection)connection;
            https.setHostnameVerifier(org.apache.commons.ssl.HostnameVerifier.DEFAULT_AND_LOCALHOST);
         }
         if (username!=null) {
            connection.setRequestProperty("Authorization", "Basic " + Base64Coder.encode(username+':'+password));
         }
         String charset = "UTF-8";
         InputStream is = connection.getInputStream();
         contentType = connection.getContentType();
         if (contentType!=null) {
            int semicolon = contentType.indexOf(';');
            if (semicolon>=0) {
               String type = contentType.substring(0,semicolon);
               String rest = contentType.substring(semicolon+1);
               int start = rest.indexOf("charset=");
               if (start>=0) {
                  charset = rest.substring(start+8);
               }
               contentType = type;
            }
         }
         return new InputStreamReader(is,charset);
      }
   }
   Map<URI,Location> locations;
   Map<URI,List<Transformer>> cache;
   ScriptCompiler compiler;
   
   /** Creates a new instance of ScriptCache */
   public ScriptCache()
   {
      locations = new TreeMap<URI,Location>();
      cache = new TreeMap<URI,List<Transformer>>();
      compiler = new ScriptCompiler();
   }
   
   public void add(URI location)
   {
      add(location,null,null,true);
   }
   
   public void add(URI location,boolean replace)
   {
      add(location,null,null,replace);
   }
   
   public void add(URI location,String username,String password,boolean replace)
   {
      if (!replace && locations.get(location)!=null) {
         return;
      }
      locations.put(location,new Location(location,username,password));
   }
   
   public boolean hasScript(URI uri) {
      return locations.get(uri)!=null;
   }
   
   public Transformer get(URI uri)
      throws IOException,TransformerConfigurationException,TransformerException
   {
      List<Transformer> cachedList = cache.get(uri);
      if (cachedList==null || cachedList.size()==0) {
         LOG.info("Loading new transformer "+uri);
         Location location = locations.get(uri);
         if (location==null) {
            return null;
         }
         Reader reader = location.open();
         Transformer transformer = transformer = compiler.compileToTransformer(new StreamSource(reader,location.location.toString()));
         synchronized (cache) {
            if (cachedList==null) {
               cachedList = new ArrayList<Transformer>();
               cache.put(uri,cachedList);
            }
         }
         return transformer;
      } else {
         synchronized (cachedList) {
            return cachedList.remove(0);
         }
      }
   }
   
   public void release(URI location,Transformer transformer)
   {
      List<Transformer> cachedList = cache.get(location);
      if (cachedList==null) {
         throw new RuntimeException("Cached list for template name "+location+" is null on release().  This shouldn't happen!");
      }
      LOG.info("Releasing "+location);
      transformer.reset();
      // Saxon doesn't reset the document pool.  Boo!
      try {
         transformer.getClass().getMethod("clearDocumentPool").invoke(transformer);
      } catch (NoSuchMethodException ex) {
         // throw this away, not Saxon
      } catch (Exception ex) {
         LOG.log(Level.SEVERE,"Cannot reset the document pool.",ex);
      }
      cachedList.add(transformer);
   }
   
   public void remove(URI location) {
      synchronized (cache) {
         locations.remove(location);
         cache.remove(location);
      }
   }
   
   public void reload(URI location) {
      List<Transformer> cachedList = cache.get(location);
      if (cachedList!=null) {
         cachedList.clear();
      }
   }
   
}
